package inteligenciaartificial;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Arrays;
import java.util.Collections;


public class Selector {
	
	public static Individuo[] seleccionElitista(Individuo[] individuos, int cantidad){
		//checks
		if (cantidad==0) return null;
		if(individuos==null || individuos.length==0) return null;

                Individuo[] individuosOrdenados= individuos.clone();
		Arrays.sort(individuosOrdenados, Collections.reverseOrder());

                Individuo[] seleccionados = new Individuo[cantidad];
		
		for(int i=0;i<cantidad;i++){
                    Individuo individuoSeleccionado= new Individuo(individuosOrdenados[i].getGenes());
                    individuoSeleccionado.setOperadorGenerador(Individuo.Operador.SeleccionElitista);
                    individuoSeleccionado.setNroGeneracionPadre(individuosOrdenados[i].getNroGeneracion());
                    individuoSeleccionado.setNroIndividuoPadre(individuosOrdenados[i].getNroIndividuo());
                    seleccionados[i]=individuoSeleccionado;
		}

		return seleccionados;
	}
	
        
	public static Individuo[] seleccionarRanking(Individuo[] individuos, int cantidad){
		
                if (cantidad==0) return null;
		if(individuos==null || individuos.length==0) return null;
                                
                Individuo[] individuosOrdenados= individuos;
		Arrays.sort(individuosOrdenados, Collections.reverseOrder());
                
                //calculo las copias esperadas para cada individuo
                double[] copiasEsperadas = new double[individuos.length];
                double Rmin = 0.2;
                for (int i=0;i<individuos.length;i++){
                    copiasEsperadas[i] = Rmin + 2*((individuos.length - (i+1))* (1-Rmin)/(individuos.length-1));                  
                }
                
                Individuo[] seleccionados = new Individuo[cantidad];
                int copiasPendientes = cantidad;
                
                //asigno las copias esperadas para cada individuo
                int i = 0;
                int l =0;
                int m = 0;
                do{
                    
                    Individuo indBuscado = individuosOrdenados[i];
                    int copias = (int) copiasEsperadas[l];
                    
                    if (copias >0){
                        do{
                            Individuo individuoSeleccionado= new Individuo(indBuscado.getGenes());
                            individuoSeleccionado.setOperadorGenerador(Individuo.Operador.SeleccionPorRanking);
                            individuoSeleccionado.setNroGeneracionPadre(indBuscado.getNroGeneracion());
                            individuoSeleccionado.setNroIndividuoPadre(indBuscado.getNroIndividuo());
                            seleccionados[m]=individuoSeleccionado;
                            
                            copias--;
                            m++;
                            copiasPendientes--;
                    
                     }while (copiasPendientes > 0 && copias > 0 && m<cantidad);
                    }
                    
                    i++;
                    l++;
                }while (copiasPendientes > 0 && i >= 0 && m< cantidad); 
            
                //si quedan copias pendientes, se selecciona por ruleta
                if (copiasPendientes > 0){
                    
                    Intervalo[] rangos = Selector.crearRangos(individuosOrdenados, copiasEsperadas);
                    
                    //se eligen al azar individuos según los rangos creados para cada uno
                    //hasta completar las copias pendientes
                    do{
//                       
                        Individuo individuoPadre = Selector.seleccionarRuleta(individuosOrdenados, rangos);
                        Individuo individuoSeleccionado= new Individuo(individuoPadre.getGenes());
                        individuoSeleccionado.setOperadorGenerador(Individuo.Operador.SeleccionPorRanking);
                        individuoSeleccionado.setNroGeneracionPadre(individuoPadre.getNroGeneracion());
                        individuoSeleccionado.setNroIndividuoPadre(individuoPadre.getNroIndividuo());
                        seleccionados[m] = individuoSeleccionado;
                        m++;
                        copiasPendientes--;
                        
                    }while (copiasPendientes >0);
                    
                }
                 return seleccionados;
	}
//        
        public static Individuo[] seleccionarRuleta(Individuo[] individuos, int cantidad){
            
            if (cantidad==0) return null;
            if(individuos==null || individuos.length==0) return null;
            
            double[] razon = new double[individuos.length];
            double total = 0;
            for (int i=0; i<individuos.length;i++){
                total = total + AptitudUtilidad.calcularAptitud(individuos[i]);
            }
            
            for (int i=0; i<individuos.length;i++){
                razon[i] = AptitudUtilidad.calcularAptitud(individuos[i])/total;
            }
             
            Intervalo[] rangos = Selector.crearRangos(individuos, razon);
            Individuo[] seleccionados = new Individuo[cantidad];
           
            for (int i=0; i<seleccionados.length;i++){ 
                  Individuo individuoPadre = Selector.seleccionarRuleta(individuos, rangos);
                  Individuo individuoSeleccionado= new Individuo(individuoPadre.getGenes());
                  individuoSeleccionado.setOperadorGenerador(Individuo.Operador.SeleccionPorRuleta);
                  individuoSeleccionado.setNroGeneracionPadre(individuoPadre.getNroGeneracion());
                  individuoSeleccionado.setNroIndividuoPadre(individuoPadre.getNroIndividuo());
                  seleccionados[i]=individuoSeleccionado;
             }
           
            return seleccionados;   
        }
//        
        private static Individuo seleccionarRuleta (Individuo[] individuos, Intervalo[] rangos){
           
            //este método elige un individuo por ruleta, según los rangos definidos 
            //pasados como parámetro
            
            int aleatorio = (int)(Math.random() * 100);
            int indiceBuscado = 0;
            for (int j = 0; j<individuos.length;j++){
               if(aleatorio>=rangos[j].getMinimo() && aleatorio<=rangos[j].getMaximo()) indiceBuscado = j;
            }

            return individuos[indiceBuscado];
        }
//        
        public static Individuo[] seleccionarPorTorneo (Individuo[] individuos, int cantidad){
            if (cantidad==0) return null;
            if(individuos==null || individuos.length==0) return null;
            
            //se establece el nro de grupos igual a la cantidad de individuos
            //que se desea seleccionar por torneo
            int cantidadGrupo = (int) (Settings.totalPoblacion / cantidad);
            int grupos = cantidad;
            Individuo[][] subgrupos = new Individuo[grupos][];
            int k = 0;
            
            //distribuyo los individuos de la población en cada grupo
            for (int i =0;i<grupos;i++){
                 if (i == (grupos-1))cantidadGrupo = Settings.totalPoblacion -k;   
                 subgrupos[i] = new Individuo[cantidadGrupo];
                 for(int j = 0; j<cantidadGrupo; j++){ subgrupos[i][j] = individuos[k]; k++;}
            }
            
            Individuo[] seleccionados = new Individuo[cantidad];
            
            //para cada subgrupo, se elige un individuo por ruleta
            for (int i=0; i<grupos;i++){
                double[] razon = new double[subgrupos[i].length];
                double total = 0;
                for (int j=0; j<subgrupos[i].length;j++){
                    total = total + AptitudUtilidad.calcularAptitud(subgrupos[i][j]);
                }
            
                for (int j=0; j<subgrupos[i].length;j++){
                    razon[j] = AptitudUtilidad.calcularAptitud(subgrupos[i][j])/total;
                }
             
                Intervalo[] rangos = Selector.crearRangos(subgrupos[i], razon);
                
                Individuo individuoPadre = Selector.seleccionarRuleta(subgrupos[i], rangos);
                Individuo individuoSeleccionado= new Individuo(individuoPadre.getGenes());
                individuoSeleccionado.setOperadorGenerador(Individuo.Operador.SeleccionPorTorneo);
                individuoSeleccionado.setNroGeneracionPadre(individuoPadre.getNroGeneracion());
                individuoSeleccionado.setNroIndividuoPadre(individuoPadre.getNroIndividuo());
                seleccionados[i]=individuoSeleccionado;
            }
            return seleccionados;
        }
//        
        public static Individuo[] controlCopiasEsperadas(Individuo[] individuos, int cantidad){
            if (cantidad==0) return null;
            if(individuos==null || individuos.length==0) return null;
            
            double aptitudPromedio = 0;
            for (int i=0; i<individuos.length;i++){
                aptitudPromedio = aptitudPromedio + AptitudUtilidad.calcularAptitud(individuos[i]);
            }
            
            aptitudPromedio = aptitudPromedio/(double)individuos.length;
            
            double[] copiasEsperadas = new double [individuos.length];
            for (int i = 0; i<individuos.length;i++){
                copiasEsperadas[i] = AptitudUtilidad.calcularAptitud(individuos[i])/aptitudPromedio;
            }
            
            int copiasPendientes = cantidad;
            int i =0;
            Individuo[] seleccionados = new Individuo[cantidad];
            int m=0;
            do{
                int copias = (int)copiasEsperadas[i];
                do{
                    Individuo individuoPadre = individuos[i];
                    Individuo individuoSeleccionado= new Individuo(individuoPadre.getGenes());
                    individuoSeleccionado.setOperadorGenerador(Individuo.Operador.SeleccionPorCopiasEsperadas);
                    individuoSeleccionado.setNroGeneracionPadre(individuoPadre.getNroGeneracion());
                    individuoSeleccionado.setNroIndividuoPadre(individuoPadre.getNroIndividuo());
                    seleccionados[m]=individuoSeleccionado;
                    
                    copias--;
                    copiasPendientes--;
                    m++;
                }while (copias>0 &&copiasPendientes>0);
                i++;
            }while (copiasPendientes>0 && i<individuos.length);
            
            
            //si quedan copias pendientes, se selecciona por ruleta
            if (copiasPendientes > 0){
                    
                    Intervalo[] rangos = Selector.crearRangos(individuos, copiasEsperadas);
                    
                    //se eligen al azar individuos según los rangos creados para cada uno
                    //hasta completar las copias pendientes
                    do{
                        Individuo individuoPadre = Selector.seleccionarRuleta(individuos, rangos);
                        Individuo individuoSeleccionado= new Individuo(individuoPadre.getGenes());
                        individuoSeleccionado.setOperadorGenerador(Individuo.Operador.SeleccionPorCopiasEsperadas);
                        individuoSeleccionado.setNroGeneracionPadre(individuoPadre.getNroGeneracion());
                        individuoSeleccionado.setNroIndividuoPadre(individuoPadre.getNroIndividuo());
                        seleccionados[m]=individuoSeleccionado;
                        
                        m++;
                        copiasPendientes--;
                        
                    }while (copiasPendientes >0);
            
            }
            return seleccionados;
        }
        
        private static Intervalo[] crearRangos(Individuo[] individuos, double[] razon){
            
            Intervalo[] rangos = new Intervalo[individuos.length];
            int total = 0;
            int[] parteDecimal=new int[individuos.length];
            DecimalFormatSymbols simbolos = new DecimalFormatSymbols();
            simbolos.setDecimalSeparator('.');
            DecimalFormat formateador = new DecimalFormat ("####.####",simbolos);
                  
            int parteEntera;
            double decimal;
                  
            //se totaliza las partes decimales de la razón de la aptitud de cada individuo
            //con respecto a la aptitud promedio o sumatoria de aptitudes (según sea el caso)
            for (int j=0; j<individuos.length;j++){
                  parteEntera = (int) razon[j];
                  decimal = new Double(formateador.format(razon[j]));
                  parteDecimal[j] = (int) ((decimal-parteEntera)*10000);
                  total = total + parteDecimal [j];
            } 
                    
            //se establecen los rangos para cada individuo
            int n = 0;
            for (int j=0; j<individuos.length; j++){
                  int max = (int)((double)parteDecimal[j]/(double)total*100)-1 + n;
                  if (((int)((double)parteDecimal[j]/(double)total*100))<1) max = n;
                  if (j == individuos.length-1) max = 99;
                  rangos[j] = new Intervalo(n,max); 
                  n = max + 1; 
             }

            return rangos;
        
        }

}
